package lumis.service.wsrp.producer;

import java.net.URL;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.portlet.PortletMode;
import javax.portlet.WindowState;
import javax.servlet.http.HttpServletRequest;

import lumis.portal.PortalContext;
import lumis.portal.PortalException;
import lumis.portal.PortalRequestParameters;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.group.GroupConfig;
import lumis.portal.manager.ManagerException;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.manager.ManagerFactoryInternal;
import lumis.portal.page.PageConfig;
import lumis.portal.page.PageWebResource;
import lumis.portal.servicecontainer.ServiceContainerActionRequest;
import lumis.portal.servicecontainer.ServiceContainerActionResponse;
import lumis.portal.servicecontainer.ServiceContainerRenderRequest;
import lumis.portal.servicecontainer.ServiceContainerRenderResponse;
import lumis.portal.serviceinterface.IServiceInterfaceRequest;
import lumis.portal.serviceinstance.ServiceInstanceConfig;
import lumis.portal.serviceinstance.acl.IServiceInstanceAclManager;
import lumis.portal.serviceinstance.acl.ServiceInstancePermissions;
import lumis.portal.serviceinterface.IServiceInterfaceResponse;
import lumis.portal.serviceinterfaceinstance.ServiceInterfaceInstanceConfig;
import lumis.portal.transaction.PortalTransactionFactory;
import lumis.portal.url.IURLManager.CreateStrategy;
import lumis.util.CookieUtil;
import lumis.util.ITransaction;
import lumis.util.log.ILogger;
import lumis.util.log.LoggerFactory;

import com.sun.portal.container.ChannelMode;
import com.sun.portal.container.ChannelState;
import com.sun.portal.container.ChannelURLFactory;
import com.sun.portal.container.ChannelURLType;
import com.sun.portal.container.ContainerException;
import com.sun.portal.container.ContentException;
import com.sun.portal.container.ExecuteActionRequest;
import com.sun.portal.container.ExecuteActionResponse;
import com.sun.portal.container.ExecuteEventRequest;
import com.sun.portal.container.ExecuteEventResponse;
import com.sun.portal.container.GetMarkupRequest;
import com.sun.portal.container.GetMarkupResponse;
import com.sun.portal.container.GetResourceRequest;
import com.sun.portal.container.GetResourceResponse;
import com.sun.portal.container.impl.AbstractContainer;

/**
 * Portlet-Container implementation used to process WSRP producer requests.
 *
 * @version $Revision: 12710 $ $Date: 2011-03-18 17:00:16 -0300 (Fri, 18 Mar 2011) $
 * @since 5.5.0
 */
class ProducerContainer extends AbstractContainer
{
	private static ILogger logger = LoggerFactory.getLogger(ProducerContainer.class);
	
	public void getMarkup(GetMarkupRequest getMarkupRequest, 
			GetMarkupResponse getMarkupResponse) throws ContainerException, ContentException
	{
		StringBuffer markup = new StringBuffer(renderServiceInterfaceInstance(getMarkupRequest, getMarkupResponse));
		getMarkupResponse.setMarkup(markup);
	}
	
	protected String renderServiceInterfaceInstance(GetMarkupRequest getMarkupRequest, 
			GetMarkupResponse getMarkupResponse) throws ContainerException, ContentException
	{
		final String serviceInterfaceInstanceId = getMarkupRequest.getEntityID().getPortletWindowName();
		try
		{
			ITransaction transaction = PortalTransactionFactory.createTransaction();
			try
			{
				transaction.begin();

				// get interfaceInstanceConfig
				SessionConfig sessionConfig = SessionConfig.getCurrentSessionConfig();
				ServiceInterfaceInstanceConfig interfaceInstanceConfig = ManagerFactory.getServiceInterfaceInstanceManager().get(sessionConfig, serviceInterfaceInstanceId, transaction);
				
				// check permission
				checkViewPermission(sessionConfig, interfaceInstanceConfig, transaction);
				
				// get webResource
				String pageId = interfaceInstanceConfig.getPageId();
				if(pageId == null)
				{
					pageId = PageConfig.PAGE_ID_ADMIN_BLANK;
				}
				final PageWebResource webResource = (PageWebResource) ManagerFactoryInternal.getURLManager().getWebResourceByDynamicPath(
						"/" + PageConfig.PAGE_MAIN + "?" + PortalRequestParameters.PAGE_PARAMETER_PAGEID + "=" + pageId, CreateStrategy.TRANSIENT);
				
				// create renderParameters
				PortalRequestParameters parameters = new PortalRequestParameters(sessionConfig, transaction);
				Map<String, String[]> renderParameters = getMarkupRequest.getRenderParameters();
				removeEmptyParameters(renderParameters);
				renderParameters.put(PortalRequestParameters.II_PARAMETER_PORTLET_MODE, new String[] {getMarkupRequest.getChannelMode().toString()});
				renderParameters.put(PortalRequestParameters.II_PARAMETER_WINDOW_STATE, new String[] {getMarkupRequest.getWindowState().toString()});
				parameters.setServiceInterfaceInstanceParameters(serviceInterfaceInstanceId, renderParameters);
				
				// create renderRequest
				final HttpServletRequest httpRequest = getMarkupRequest.getHttpServletRequest();
				ServiceContainerRenderRequest renderRequest = new ServiceContainerRenderRequest(sessionConfig, parameters, httpRequest);
				renderRequest.setPageWebResource(webResource);
				renderRequest.setServiceInterfaceInstanceId(serviceInterfaceInstanceId);
				renderRequest.setSessionConfig(sessionConfig);
				
				// update preview information in renderRequest
				if("1".equals(CookieUtil.getCookie(httpRequest, PortalRequestParameters.PAGE_PARAMETER_DATA_PREVIEW_MODE)) || parameters.getPageParameterValue(PortalRequestParameters.PAGE_PARAMETER_PREVIEW_ITEMID) != null)
				{
					if(ManagerFactory.getGroupManager().isMember(sessionConfig, GroupConfig.GROUP_PUBLISHERS_ID, sessionConfig.getUserId(), transaction))
						renderRequest.setPreview(true);
				}
				
				// create renderResponse
				final ChannelURLFactory channelURLFactory = getMarkupRequest.getChannelURLFactory();
				ServiceContainerRenderResponse renderResponse = new ProducerRenderResponse(renderRequest, getMarkupRequest, getMarkupResponse, channelURLFactory);
				renderResponse.setPageWebResource(webResource);
				renderResponse.setServiceInterfaceInstanceId(serviceInterfaceInstanceId);
				renderResponse.setNamespace(MarkupManagerImpl.getNamespacePrefix(httpRequest));

				// render the interface instance
				String result;
				try
				{
					result = PortalContext.getServiceContainer().renderServiceInterfaceInstance(
							renderRequest, renderResponse, transaction);
				}
				catch (PortalException e)
				{
					// service container already logged this exception
					// throw exception appropriate to WSRP semantics
					String msg = "Exception thrown while rendering service interface instance '" + serviceInterfaceInstanceId + "'";
					throw new ContentException(msg, e);
				}
				
				transaction.commit();
				
				result = fixHtmlMarkup(result, renderRequest, renderResponse);
				return result;
			}
			finally
			{
				transaction.dispose();
			}
		}
		catch(ContentException e)
		{
			throw e;
		}
		catch(ContainerException e)
		{
			throw e;
		}
		catch(Exception e)
		{
			String msg = "Exception thrown while preparing to render service interface instance " + serviceInterfaceInstanceId;
			logger.error(msg, e);
			throw new ContainerException(msg, e);
		}
	}

	/**
	 * Checks if the user has view permission for the interface's service instance.
	 * @param sessionConfig the user session information.
	 * @param interfaceInstanceConfig the interface instance.
	 * @param transaction the transaction for persistence access.
	 * @throws ProducerAccessDeniedException if the user does not have the permission.
	 * @since 5.5.0
	 */
	private void checkViewPermission(SessionConfig sessionConfig, ServiceInterfaceInstanceConfig interfaceInstanceConfig, ITransaction transaction) throws ManagerException, PortalException,
			ProducerAccessDeniedException
	{
		ServiceInstanceConfig serviceInstanceConfig = ManagerFactory.getServiceInstanceManager().get(interfaceInstanceConfig.getServiceInstanceId(), transaction);
		IServiceInstanceAclManager serviceInstanceAclManager = ManagerFactory.getServiceInstanceAclManager();
		ServiceInstancePermissions permissions = serviceInstanceAclManager.getPermissions(sessionConfig, serviceInstanceConfig.getServiceId(), transaction);
		if (!serviceInstanceAclManager.checkPermission(sessionConfig, interfaceInstanceConfig.getServiceInstanceId(), permissions.VIEW_SERVICE_INSTANCE, transaction))
		{
			throw new ProducerAccessDeniedException("User '" + sessionConfig.getUserId() + 
					"' does not have VIEW permission for service instance '" + serviceInstanceConfig.getId() + 
					"' (" + serviceInstanceConfig.getName() + ").");
		}
	}
	
	private static final Pattern PATTERN_ATTRIBS_MISC = Pattern.compile(
			"((?:src|background|action|href)\\s*=\\s*(?:\"|'|\\\\\"|\\\\'))(?!\\s*" +
			ProducerServiceInterfaceURL.PRODUCER_GENERATED_URL_PREFIX + 
			"|\\s*https?:|\\s*javascript:|\\s*news:|\\s*ftp:|\\s*file:|#|mailto:|\\\\\\\\)([^'\"\\\\]+)");
	private static final Pattern PATTERN_BACKGROUND_URL = Pattern.compile(
			"(background:url\\()(?!" + ProducerServiceInterfaceURL.PRODUCER_GENERATED_URL_PREFIX + "|https?:)");
	private static final Pattern PATTERN_JAVASCRIPT_MISC = Pattern.compile(
			"((location\\.replace|location\\.assign|window\\.open)\\s*\\(\\s*(\"|'|&quot;))(?!\\s*" +
			ProducerServiceInterfaceURL.PRODUCER_GENERATED_URL_PREFIX + 
			"|\\s*https?:|\\s*javascript:|\\s*news:|\\s*ftp:|\\s*file:|#|mailto:|\\\\\\\\)");
	private static final Pattern PATTERN_PRODUCER_GENERATED_URL_PREFIX = Pattern.compile(ProducerServiceInterfaceURL.PRODUCER_GENERATED_URL_PREFIX, Pattern.LITERAL);
	
	/**
	 * Pattern that finds the ampersand XML entity (&amp;amp;).
	 * @since 5.5.0
	 */
	private static final Pattern PATTERN_AMPERSAND_ENTITY = Pattern.compile("&amp;", Pattern.LITERAL);

	/**
	 * Perform some adjustments in interface's markup.
	 * 
	 * @param markup the original service interface instance markup.
	 * @param request the interface request.
	 * @param response the interface response.
	 * @return the adjusted markup.
	 * @since 5.5.0
	 */
	private String fixHtmlMarkup(String markup, IServiceInterfaceRequest request, IServiceInterfaceResponse response)
	{
		StringBuffer buf = new StringBuffer(markup.length() * 2);
		
		// include LumisPortalWSRP.js at the start of the markup
		buf.append("<script src=\"" + response.encodeURL(request.getContextPath() + "/lumis/portal/client/script/LumisPortalWSRP.js") + 
				"\" charset=\"UTF-8\" type=\"text/javascript\"></script>\n");
		
		// append the markup calling encodeURL for relative URLs
		Matcher matcher = PATTERN_ATTRIBS_MISC.matcher(markup);
		while (matcher.find())
		{
			String originalURI = matcher.group(2);
			String uri = request.getContextPath() + "/" + PATTERN_AMPERSAND_ENTITY.matcher(originalURI).replaceAll("&");
			matcher.appendReplacement(buf, "$1" + response.encodeURL(uri));
		}
		matcher.appendTail(buf);
		markup = buf.toString();
		
		// transform some relative URLs in absolute URLs (directly to this producer)
		String prefix = PortalContext.getFrameworkUrl() + "/";
		markup = PATTERN_JAVASCRIPT_MISC.matcher(markup).replaceAll("$1" + prefix);
		markup = PATTERN_BACKGROUND_URL.matcher(markup).replaceAll("$1" + prefix);
		
		// remove PRODUCER_GENERATED_URL_PREFIX from markup
		markup = PATTERN_PRODUCER_GENERATED_URL_PREFIX.matcher(markup).replaceAll("");
		
		return markup;
	}

	public void executeAction(ExecuteActionRequest executeActionRequest, 
			ExecuteActionResponse executeActionResponse, 
			ChannelURLType channelURLType) throws ContainerException, ContentException
	{
		String serviceInterfaceInstanceId = executeActionRequest.getEntityID().getPortletWindowName();
		
		if (!ChannelURLType.ACTION.equals(channelURLType))
			throw new ContainerException("Invalid channelURLType: " + channelURLType);
		
		try
		{
			ITransaction transaction = PortalTransactionFactory.createTransaction();
			try
			{
				transaction.begin();

				// get interfaceInstanceConfig
				SessionConfig sessionConfig = SessionConfig.getCurrentSessionConfig();
				ServiceInterfaceInstanceConfig interfaceInstanceConfig = ManagerFactory.getServiceInterfaceInstanceManager().get(sessionConfig, serviceInterfaceInstanceId, transaction);
				
				// check permission
				checkViewPermission(sessionConfig, interfaceInstanceConfig, transaction);

				// get webResource
				String pageId = PageConfig.PAGE_ID_ADMIN_BLANK;
				final PageWebResource webResource = (PageWebResource) ManagerFactoryInternal.getURLManager().getWebResourceByDynamicPath(
						"/" + PageConfig.PAGE_MAIN + "?" + PortalRequestParameters.PAGE_PARAMETER_PAGEID + "=" + pageId, CreateStrategy.TRANSIENT);
				
				// create parameters
				PortalRequestParameters parameters = new PortalRequestParameters(sessionConfig, transaction);
				Map<String, String[]> actionParameters = executeActionRequest.getActionParameters();
				removeEmptyParameters(actionParameters);
				actionParameters.put(PortalRequestParameters.II_PARAMETER_PORTLET_MODE, new String[] {executeActionRequest.getChannelMode().toString()});
				actionParameters.put(PortalRequestParameters.II_PARAMETER_WINDOW_STATE, new String[] {executeActionRequest.getWindowState().toString()});
				parameters.setServiceInterfaceInstanceParameters(serviceInterfaceInstanceId, actionParameters);

				// create actionRequest
				final HttpServletRequest httpRequest = executeActionRequest.getHttpServletRequest();
				ServiceContainerActionRequest actionRequest = new ServiceContainerActionRequest(sessionConfig, parameters, httpRequest);
				actionRequest.setPageWebResource(webResource);
				actionRequest.setServiceInterfaceInstanceId(serviceInterfaceInstanceId);
				actionRequest.setSessionConfig(sessionConfig);
				
				// create actionResponse
				final ChannelURLFactory channelURLFactory = executeActionRequest.getChannelURLFactory();
				ServiceContainerActionResponse actionResponse = new ProducerActionResponse(
						actionRequest, executeActionRequest, executeActionResponse, channelURLFactory);
				actionResponse.setPageWebResource(webResource);
				actionResponse.setServiceInterfaceInstanceId(serviceInterfaceInstanceId);
				actionResponse.setNamespace(MarkupManagerImpl.getNamespacePrefix(httpRequest));

				// update preview information in the request
				if("1".equals(CookieUtil.getCookie(httpRequest, PortalRequestParameters.PAGE_PARAMETER_DATA_PREVIEW_MODE)) || parameters.getPageParameterValue(PortalRequestParameters.PAGE_PARAMETER_PREVIEW_ITEMID) != null)
				{
					if(ManagerFactory.getGroupManager().isMember(sessionConfig, GroupConfig.GROUP_PUBLISHERS_ID, sessionConfig.getUserId(), transaction))
						actionRequest.setPreview(true);
				}
				
				// execute the process action
				try
				{
					PortalContext.getServiceContainer().processServiceInterfaceInstanceAction(actionRequest, actionResponse, transaction);
				}
				catch (PortalException e)
				{
					// service container already logged this exception
					// throw exception appropriate to WSRP semantics
					String msg = "Exception thrown while executing process action for service interface instance " + serviceInterfaceInstanceId;
					throw new ContentException(msg, e);
				}
				
				// update executeActionResponse
				PortletMode newPortletMode = actionResponse.getPortletMode();
				if (newPortletMode != null)
					executeActionResponse.setNewChannelMode(new ChannelMode(newPortletMode.toString()));
				WindowState newWindowState = actionResponse.getWindowState();
				if (newWindowState != null)
					executeActionResponse.setNewWindowState(new ChannelState(newWindowState.toString()));
				String redirectDestination = actionResponse.getRedirectDestination();
				if (redirectDestination != null)
					executeActionResponse.setRedirectURL(new URL(redirectDestination));
				executeActionResponse.setRenderParameters(actionResponse.getRenderParameterMap());
				
				transaction.commit();
			}
			finally
			{
				transaction.dispose();
			}
		}
		catch(ContentException e)
		{
			throw e;
		}
		catch(ContainerException e)
		{
			throw e;
		}
		catch(Exception e)
		{
			String msg = "Exception thrown while preparing to execute process action for service interface instance " + serviceInterfaceInstanceId;
			logger.error(msg, e);
			throw new ContainerException(msg, e);
		}
	}
	
	/**
	 * Remove empty parameters from the given map. A parameter is considered
	 * empty if its value is an array with only one element that is either
	 * an empty string or null.
	 * @since 5.5.0
	 */
	private void removeEmptyParameters(Map<String, String[]> parameters)
	{
		for (Iterator<String[]> itParameters = parameters.values().iterator(); itParameters.hasNext();)
		{
			String[] valueArray = itParameters.next();
			
			// if the valueArray is {""} or {null}, remove the parameter
			if (valueArray != null && valueArray.length == 1)
			{
				String value = valueArray[0];
				if (value == null || value.length() == 0)
					itParameters.remove();
			}
		}
	}

	public void executeEvent(ExecuteEventRequest request, ExecuteEventResponse response) throws ContainerException, ContentException
	{
		if (logger.isDebugEnabled())
			logger.debug("Ignoring executeEvent called for " + request.getEntityID());
	}

	public void getResources(GetResourceRequest request, GetResourceResponse response) throws ContainerException, ContentException
	{
		throw new ContainerException("getResources operation is unsupported");
	}
}
